using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Microblaze.Simulator.Engine;

namespace Weazel.Microblaze.Simulator.Components.Microblaze.Instructions
{
    public class Bri : Instruction
    {
        private enum BriInstructionKind
        {
            None,   // error
            bri,    // branch immediate
            brai,   // branch absolute immediate
            brid,   // branch immediate with delay
            braid,  // branch absolute immediate with delay
            brlid,  // branch and link immediate with delay
            bralid  // branch absolute and like immediate with delay
        }

        private BriInstructionKind kind;


        public override int CycleCount
        {
            get
            {
                return
                  kind == BriInstructionKind.braid ||
                  kind == BriInstructionKind.bralid ||
                  kind == BriInstructionKind.brid ||
                  kind == BriInstructionKind.brlid ? 2 : 3;
            }
        }

        public Bri()
            : base("bri")
        {
        }

        public override void Set(uint address, uint value)
        {
            base.Set(address, value);

            uint bits11to16 = (value >> 16) & 0x1F;

            kind =
              (bits11to16 == 0x00) ? BriInstructionKind.bri :
              (bits11to16 == 0x08) ? BriInstructionKind.brai :
              (bits11to16 == 0x10) ? BriInstructionKind.brid :
              (bits11to16 == 0x18) ? BriInstructionKind.braid :
              (bits11to16 == 0x14) ? BriInstructionKind.brlid :
              (bits11to16 == 0x1C) ? BriInstructionKind.bralid :
              BriInstructionKind.None;

            if (kind == BriInstructionKind.None)
                throw new UnknownInstructionException(value);
        }

        /// <summary>
        /// Description
        /// Branch to the instruction located at address determined by IMM, sign-extended to 32 bits.
        /// The mnemonics brlid and bralid will set the L bit. If the L bit is set, linking will be
        /// performed. The current value of PC will be stored in rD.
        /// The mnemonics brai, braid and bralid will set the A bit. If the A bit is set, it means that the
        /// branch is to an absolute value and the target is the value in IMM, otherwise, it is a relative
        /// branch and the target will be PC + IMM.
        /// The mnemonics brid, braid, brlid and bralid will set the D bit. The D bit determines
        /// whether there is a branch delay slot or not. If the D bit is set, it means that there is a delay
        /// slot and the instruction following the branch (i.e. in the branch delay slot) is allowed to
        /// complete execution before executing the target instruction. If the D bit is not set, it means
        /// that there is no delay slot, so the instruction to be executed after the branch is the target
        /// 
        /// </summary>
        public override void Execute(MicroblazeComponent MicroblazeComponent)
        {
            bool L =
              kind == BriInstructionKind.bralid ||
              kind == BriInstructionKind.brlid;

            bool A =
              kind == BriInstructionKind.brai ||
              kind == BriInstructionKind.braid ||
              kind == BriInstructionKind.bralid;

            bool D =
              kind == BriInstructionKind.braid ||
              kind == BriInstructionKind.bralid ||
              kind == BriInstructionKind.brid ||
              kind == BriInstructionKind.brlid;


            if (L)
            {
                MicroblazeComponent.WriteRegister(DestinationRegister, MicroblazeComponent.Pc, Address);
            }

            uint imm = GetImmediateValue(MicroblazeComponent);

            if (A)
            {
                MicroblazeComponent.Pc = imm;
            }
            else
            {
                MicroblazeComponent.Pc = MicroblazeComponent.Pc + imm;
            }

            if (D)
            {
                MicroblazeComponent.DelayedBranchActive = true;
            }
        }

        public override string ToString()
        {
            return
              kind == BriInstructionKind.brlid ?
                string.Format("brlid r{0}, {1}", DestinationRegister, ImmediateValue) :
              kind == BriInstructionKind.bralid ?
                string.Format("brlid r{0}, {1}", DestinationRegister, ImmediateValue) :
              string.Format("{0} {1}", kind, ImmediateValue);
        }
    }
}
